有鑑於我們在 <CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 中已經說過了關於 CI/CD 的整合與流程概念,我們接下來簡單的說一下流程並且整理出碎片化的 Jobs,然後進入到結合<Infrastructure as Code : Terraform 基礎設施代碼化與版本管控> 、<CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 、的多環境應用 藍綠部屬
環節
重新複習一下,CI/CD 流程最重要的理念就是 建立一個穩定且固定的商業邏輯驗證交付流程,
積極性的保護既有商業邏輯不被程式碼異動所破壞汙染
`
每當有程式碼被推送(Push)到程式碼倉庫(例如 GitHub),流水線就自動啟動、它會抓取最新的程式碼並執行編譯、執行單元測試、程式碼品質掃描等一系列「品質檢查」。這就像是食品工廠的「品管環節」,每當有新的原料送達,品管員會立刻抽樣檢驗,確保這批原料沒有問題,不會污染整個生產線 - CI 的目標是 儘早發現問題 。當 CI 品管環節全部通過後,流水線會繼續下一步:打包(建立 Docker Image)、推送至倉庫(ECR)、然後自動觸發對 Dev -> Staging -> Prod 環境的部署,CD 的目標是讓發布成為一個 日常 、 穩定 且 可追蹤的事件,就像品管合格的原料,會被自動送上生產線,加工、烹飪、真空包裝,最後由輸送帶直接送到貨車上,配送至各大門市。
# .github/workflows/deploy.yml
name: Multi-Environment Deployment
on:
push:
branches: [release, develop]
pull_request:
branches: [release]
env:
AWS_REGION: us-west-2
ECR_REPOSITORY: demo-app
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.build.outputs.image-tag }}
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push Docker image
id: build
run: |
IMAGE_TAG=${GITHUB_SHA:0:8}
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
deploy-dev:
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-latest
environment: development
steps:
- name: Deploy to Development
run: |
# ECS deployment
aws ecs update-service \
--cluster dev-cluster \
--service app-service \
--task-definition app-service:LATEST
deploy-staging:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to Staging
run: |
# Kubernetes deployment
kubectl set image deployment/app-deployment \
app=$ECR_REGISTRY/$ECR_REPOSITORY:${{ needs.build.outputs.image-tag }} \
-n staging
deploy-prod:
if: github.ref == 'refs/heads/main'
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to Production
run: |
# Blue-Green deployment
./scripts/blue-green-deploy.sh ${{ needs.build.outputs.image-tag }}
而根據我們在 <CI/CD 全自動化實作 - GitHub Actions × CodePipeline × CodeBuild> 中所說的,<企業級 Jobs 模組化與跨領域引用> 當我們需要部署的時候,我們可以抽出共同流程(Jobs)將其流水線化抽出管理,並根據當前分支的行為進行自動化部屬
# build
parameters:
- name: ngCliVersion
type: string
default: latest
values:
- latest
- 16
- 12
- 11
jobs:
build:
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.build.outputs.image-tag }}
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push Docker image
id: build
run: |
IMAGE_TAG=${GITHUB_SHA:0:8}
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image-tag=$IMAGE_TAG" >> $GITHUB_OUTPUT
# deploy-dev
parameters:
- name: ngCliVersion
type: string
default: latest
values:
- latest
- 16
- 12
- 11
jobs:
deploy-dev:
if: github.ref == 'refs/heads/develop'
needs: build
runs-on: ubuntu-latest
environment: development
steps:
- name: Deploy to Development
run: |
# ECS deployment
aws ecs update-service \
--cluster dev-cluster \
--service app-service \
--task-definition app-service:LATEST
# deploy-staging
parameters:
- name: ngCliVersion
type: string
default: latest
values:
- latest
- 16
- 12
- 11
jobs:
deploy-staging:
if: github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to Staging
run: |
# Kubernetes deployment
kubectl set image deployment/app-deployment \
app=$ECR_REGISTRY/$ECR_REPOSITORY:${{ needs.build.outputs.image-tag }} \
-n staging
# deploy-prod
parameters:
- name: ngCliVersion
type: string
default: latest
values:
- latest
- 16
- 12
- 11
jobs:
deploy-prod:
if: github.ref == 'refs/heads/main'
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to Production
run: |
# Blue-Green deployment
./scripts/blue-green-deploy.sh ${{ needs.build.outputs.image-tag }}
抽出完的流程會像是這樣
# .github/workflows/deploy.yml
name: Multi-Environment Deployment
on:
push:
branches: [release, develop]
pull_request:
branches: [release]
env:
AWS_REGION: us-west-2
ECR_REPOSITORY: demo-app
jobs:
- template: jobs/build.yml@templates
- template: jobs/deploy-dev.yml@templates
- template: jobs/deploy-staging.yml@templates
parameters:
Version: ${{ variables.Version }}
env: $(env)
hasPreview: true
在上述我們簡單的複習了一下 CI/CD 流程與 Jobs
獨立切分之後,我們來了解多環境部屬的實際應用 - 藍綠部屬
。
現行系統開發中有兩大策略脈絡,其一是根據 降流直至服務徹底降溫
之後再部署的 滾動更新,而另一個則是 零停機時間
的發布策略 - 藍綠部署 (Blue/Green Deployment) 。滾動更新就像是在 F1 賽車時,只有進入到維修站並處於完全靜止( 降流直至服務徹底降溫
) 的狀態下才能允許 快速進行維修與更新
,在維修站中要與倒數的分秒進行賽跑,一旦出現失誤都將導致衝出維修站的時間被延宕,甚至如果開上賽道才發現第二個輪胎時發現新輪胎有問題,車子已經處於不穩定的狀態,要換回來必須要再次進入到維修站才能進行調整 - 而更糟的狀況是我們要在高速行駛中的 賽道上緊急進行搶修
。
而 藍綠部署
則提供了一種更安全、更優雅的哲學,我們不在行駛中的賽車( 藍色環境
)上動手。我們在旁邊的車道,準備一台一模一樣的、但安裝了新輪胎的新車( 綠色環境
)。我們讓這台新車發動、預熱、檢查所有儀表板,當我們 100% 確認新車完美無瑕時(這當然是最理想的狀況,哈哈),我們逐步將壓力切換到新車上。舊車則在旁邊逐步降載,萬一新車有問題,我們可以再 瞬間切換回來
。
執行步驟:
初始狀態: 假設我們當前的生產環境是 V1,我們稱之為藍色環境。所有用戶流量都通過 Application Load Balancer (ALB) 指向藍色環境的 Target Group。
部署綠色環境:
AWS CodeDeploy 這個服務與 ECS 和 EC2 深度整合,可以將上述複雜的藍綠部署流程自動化,我們只需要做一些簡單的配置即可。
我們剛剛討論的這套 「以 IaC 管理環境狀態、以 CI/CD 管理部署生命週期、逐步驗證環境差異」
的分工協作模式,正是當前雲端原生(Cloud-Native)領域中,討論度最高的、公認的 最佳化實現方案 (Optimal Implementation) 和 黃金標準 (Gold Standard) 。
「最佳化」並非指它是「最簡單」或「最快速建立」的方案,而是指它在幾個關鍵的、往往相互衝突的工程目標之間,取得了最優雅的平衡。首先,它在 「穩定性」 與 「敏捷性」 之間取得平衡,在過去想讓系統穩定,就意味著要減少變更,部署週期可能是數月一次。想要快速迭代(敏捷),又常常會犧牲穩定性,導致線上頻繁出錯。而現代化雲原生作法透過了 IaC 保障了「穩定性」的基石,我們的基礎設施不再是個黑盒子,每一次變更都有紀錄、有審核 (Code Review)、可追溯。這杜絕了因「手動誤操作」或「環境不一致」導致的大部分穩定性問題 - 基礎設施變得像岩石一樣穩固。CI/CD 提供了「敏捷性」的引擎,在穩固的基石上自動化流水線讓應用程式的部署變得極快且低風險。開發者可以每天進行數十次部署,而不用擔心會搞垮底層設施,藍綠部署等策略更是為這種高速迭代提供了頂級的安全網。
其次,需求團隊(Require)和維運團隊(Maintain)之間常常存在鴻溝。需求者想快速兌換新功能,維運者想穩定既有功能,互相指責、效率低下是常態。但 「以 IaC 管理環境狀態、以 CI/CD 管理部署生命週期、逐步驗證環境差異」
在 「職責清晰」 與 「團隊協作」 之間取得平衡,透過清晰的界線 (Clear Boundary)我們的分工模式提供了一個極其清晰的「契約」:
平台團隊提供穩定的平台後,應用團隊就可以透過 CI/CD 管道進行「自助式部署」,而不需要每次都去麻煩平台團隊,極大地提升了開發自主性和效率。
最後則是在 「可控性」 與 「複雜性」 之間取得平衡,現代雲端系統的確非常複雜,動輒數百個資源和設定。雖然初學有門檻,但 IaC 是管理這種複雜度的唯一有效方法,它將龐雜的雲端資源,轉化為人類可讀、機器可執行的結構化文本。我們可以審視、測試、模組化我們的基礎設施,就像對待任何軟體一樣。同時 CI/CD 隱藏了複雜性,對於大多數開發者來說,他們不需要了解藍綠部署背後複雜的 ALB 規則切換細節。他們只需要知道「當我把程式碼合併到 main 分支,並在 Pipeline 點下『批准』按鈕後,我的新功能就會安全上線」- CI/CD 管道將複雜的部署過程,封裝成一個簡單、可靠的自動化流程。
現在我們知道了藍綠部署的「哲學」與在 ECS 和 EC2 層級上的應用,現在就來看看 Kubernetes 這位「美食廣場總管」是如何用它手裡的工具,來具體執行這個精密的流量切換操作的。
與 ECS 不同,Kubernetes 本身沒有一個叫做 blue-green-deployment 的現成資源。但是,它提供了一系列更靈活、更強大的基礎元件(我們稱之為 primitives),讓我們可以像組合樂高一樣,搭建出比 AWS CodeDeploy 更靈活的藍綠部署流程,我們將重點介紹業界最主流、最能體現 Kubernetes 設計哲學的兩種實現方式:Service 層切換
和 Ingress 層切換
。
複習一下,Kubernetes 裡的一個關鍵思想: Deployment/Pods (應用實例)
是實際在運行的「攤位」,它們是 會變動、 會生滅 的。而 Service/Ingress (服務入口)
是提供給內外部顧客的「固定攤位號碼」或「美食廣場入口」,它們應該是 穩定不變的 。K8s 藍綠部署的精髓,就在於保持「服務入口」不變,只改變它背後指向的「應用實例」。
這是最能體現 Kubernetes 核心概念的方法,透過修改 Service 的 selector(標籤選擇器)來瞬間切換流量。依照我們之前在討論 EKS 時使用的案例 - 美食廣場,美食廣場的「B-52 叫號器」(Service) 本身不動,我們只是在它的後台系統裡,把服務人員從「藍色制服團隊」瞬間切換到「綠色制服團隊」。
示意 Steps:
Deployment-Blue
,它掌管著 v1
版本的 Pods。這些 Pods 身上都貼著標籤 version: blue
。# deployment-blue.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: blue # 關鍵標籤
spec:
containers:
- name: myapp
image: my-app:v1
Service
,它的 selector
只會尋找身上貼有 version: blue
標籤的 Pods
,並將流量轉發給它們。# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-service # 這是穩定不變的入口
spec:
selector:
app: myapp
version: blue # 目前指向藍色
ports:
- protocol: TCP
port: 80
targetPort: 8080
此時,所有流量都流向 v1
的 Pods
。Deployment-Green
,它掌管著 v2
版本的 Pods
,這些 Pods
的標籤是 version: green
。# deployment-green.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: green # 關鍵標籤
spec:
containers:
- name: myapp
image: my-app:v2
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'
Kubernetes
的網路核心會瞬間更新路由規則。所有之後流向 myapp-service
這個穩定入口的流量,都會被立刻導向 version: green
的 Pods
。切換完成!v2
版本出現問題,回滾操作同樣簡單快速:kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"blue"}}}'
v2
穩定運行後,CI/CD 管道就可以安全地刪除舊的藍色環境:kubectl delete deployment myapp-blue
這種方法更上一層樓,它不動 Service,而是修改 Ingress 的路由規則。 這次,「藍色團隊」和「綠色團隊」有各自獨立的內部叫號器 ( service-blue
和 service-green
)。我們去修改的是美食廣場總入口的導覽看板 (Ingress),將上面「A+ 炸雞排」的指向,從「藍色櫃檯」改成「綠色櫃檯」。
示意 Steps:
deployment-blue
和 deployment-green
。Service:service-blue
指向 version: blue
的 Pods,service-green
指向 version: green
的 Pods
。Ingress
資源,其規則指向 service-blue
。# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-blue # 指向藍色服務
port:
number: 80
Ingress
資源,將 backend.service.name
從 service-blue
改為 service-green
。# 簡易範例,實務上會用 kubectl patch 或 apply -f
# 修改 ingress.yaml 中的 service name 後執行 apply
kubectl apply -f ingress.yaml
這種模式是實現金絲雀發布 (Canary Release) 的基礎,我們可以配置 Ingress,將 95% 的流量發送到 service-blue,5% 的流量發送到 service-green,實現小規模灰度驗證。也因為 Service 層的變動較少,更符合其「穩定入口」的定位,職責更清晰。
我們現在掌握的這套結合 IaC、容器化、CI/CD 和高級部署策略
的思維模型,不僅僅是一套技術操作指南,它是一種 現代軟體工程的哲學 。它是 Google、Netflix、Amazon 等頂級科技公司能夠每週進行數千次部署,同時依然保持系統高度穩定性的核心秘訣。因為它並非單純地解決一個技術問題,而是在解決一個系統性工程問題,它深刻理解到軟體交付不僅僅是寫程式碼,還包括了測試、部署、維運、團隊協作、風險管控等一系列環節。
在未來的職業生涯中,無論工具如何演進(從 Terraform 到 OpenTofu,從 GitHub Actions 到 GitLab CI),這背後關於 狀態與流程分離
、 聲明式與命令式協作
的核心思想,將會長期適用,並成為構建高品質軟體系統的堅實基礎。工具會隨著時代演進,但設計哲學是邏輯的推導辯證過程,就像
《荀子·儒效》:“千舉萬變,其道一也。”
又或是我較喜歡的莊子說的
《莊子·天下》:“不離於宗,謂之天人。”
今天(?)的最後,我們來說說關於 環境參數
、 授權公鑰
與 連線端口
該怎麼管理吧
到目前為止,我們的自動化美食廣場已經非常先進了:有標準化的攤位藍圖 (IaC)、標準化的料理包 (Docker)、高效的總管 (ECS/EKS),還有一條全自動的配送流水線 (CI/CD)。
但現在,我們要處理一個極度敏感且重要的問題: 每個攤位的「獨家秘方」(Secrets) 要放在哪裡?
我們總不能把「A+ 炸雞排」的獨門醃料配方,用一張紙條貼在 Dockerfile
或 Git
倉庫的牆上吧?這絕對是安全上的災難。所以接下來我們要學習蟹老闆的精神,死死捂好我們的機密資料,將「配置 (Configuration)」與「秘密 (Secrets)」從我們的應用程式碼中分離出來,並在需要時才「注入」給對的應用。
K8s 的原生解決方案 — ConfigMap & Secret
Kubernetes 提供了兩種原生的資源,來處理這個問題。
ConfigMap - 「公開的菜單與公告」
Secret - 「鎖在普通抽屜裡的秘方」
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: ${ENVIRONMENT}
data:
NODE_ENV: ${ENVIRONMENT}
LOG_LEVEL: ${LOG_LEVEL}
API_BASE_URL: ${API_BASE_URL}
---
apiVersion: v1
kind: Secret
metadata:
name: database-secret
namespace: ${ENVIRONMENT}
type: Opaque
data:
url: ${DATABASE_URL_BASE64}
但對於一個嚴肅的生產環境,單純使用 K8s Secret 是不夠的。就像我們剛剛說的 防君子不防小人,主要防止的是資訊的意外洩漏
,這就像把秘方鎖在一個誰都可以輕易撬開的抽屜裡。我們還缺少了:
AWS 的頂級保險櫃 — Secrets Manager
為了解決上述問題,雲端服務商提供了專門的「秘密管理服務」。在 AWS,這個服務就是 AWS Secrets Manager。
# terraform/secrets.tf
resource "aws_secretsmanager_secret" "database_url" {
name = "${var.environment}/database-url"
description = "Database connection string for ${var.environment}"
tags = {
Environment = var.environment
Project = "multi-env-demo"
}
}
resource "aws_secretsmanager_secret_version" "database_url" {
secret_id = aws_secretsmanager_secret.database_url.id
secret_string = jsonencode({
url = var.database_url
})
}
最佳實踐:讓 K8s Pod 直接從 AWS 保險櫃取貨
現在,我們有了 K8s 的應用 (Pod),也有了 AWS 的保險櫃 (Secrets Manager)。問題是,如何讓 Pod 安全地拿到保險櫃裡的秘方,而不是透過不可靠的人工傳遞?
這就要用到一個關鍵的「橋樑」技術:IAM Roles for Service Accounts (IRSA),以及一個重要的工具:AWS Secrets and Configuration Provider (ASCP)。
就像我們不會把保險櫃的密碼告訴攤位廚師(Pod)。相反的,我們給這位廚師頒發一張特殊的 「授權員工卡」(Service Account with an IAM Role) 。當廚師需要秘方時,他拿著這張卡去刷銀行的特定閘門(ASCP),銀行系統驗證卡片權限後,會自動把今天需要用的那一頁秘方,臨時放到他面前一個專用的、有鎖的盒子里(掛載為 Pod 內的檔案)。廚師用完後盒子就銷毀。
運作流程:
設定 IRSA:
在 Deployment 中指定 ServiceAccount:
使用 ASCP 掛載秘密:
你需要先在 EKS 叢集中安裝 AWS Secrets and Configuration Provider 這個附加元件。
然後,在你的 deployment.yaml 中,定義一個 Volume,告訴 ASCP:
1. 我要掛載一個秘密。
2. 秘密的名稱是 my-app/db-password (在 Secrets Manager 中的名稱)。
3. 請把它掛載到 Pod 裡的 /etc/secrets/db-password 這個路徑。
應用程式讀取秘密: - 你的應用程式啟動後,它不再從環境變數讀取密碼,而是直接去讀取本地檔案 /etc/secrets/db-password 的內容。
這個方案的巨大優勢:
最後我們建立了一個層次分明的配置與秘密管理策略:
ConfigMap: 用於存放那些公開無妨的應用程式配置。
K8s Secret: 作為一個基礎的秘密管理工具,主要靠 RBAC 保護,適用於一些敏感度不高的場景。
AWS Secrets Manager + IRSA + ASCP 是在 EKS 上管理高敏感性秘密的黃金標準和最佳實踐。它提供了端到端的加密、稽核與自動輪換能力,是構建安全可靠系統的必要一環。
接下來讓我們暫且賣個關子,將 監控與日誌管理 、 安全性最佳實踐 、災難恢復與備份 這三個主題放在未來 發佈與運營監控(安全與合規) 這個階段的時候詳細談談,我們今天(?) 最主要討論的是 軟體開發與持續整合 - Dev / Staging / Prod 多環境治理與架構策略。 而明天我們將討論的是 軟體開發與持續整合 最後一個內容 - 開發者體驗(DX)優化:內部工具與排錯設計。